package interpreter;

import java.util.Iterator;
import java.util.Stack;
import java.util.Vector;

/**
 * <p>
 * The RunTimeStack class maintains the stack of active frames; when we call a
 * function we'll push a new frame on the stack; when we return from a function
 * we'll pop the top frame.
 * </p><p>
 * Based on interface given in class reader
 * </p>
 *
 * @author Kristofer Selbekk
 */
public class RunTimeStack {

    // Instance variables
    private Stack framePointers;
    private Vector runStack;

    /**
     * CONSTRUCTOR:
     *
     * Initializes stacks, loads first framePointer at index 0
     */
    public RunTimeStack() {
        framePointers = new Stack();
        runStack = new Vector();
        framePointers.push( new Integer(0) ); // The main frame
    }

    /**
     * Dump the RunTimeStack information for debugging
     */
    public String dump() {
        return toString();
    }

    /**
     * Returns the top item on the runtime stack, but does not
     * remove it from the stack.
     * @return the top item on the stack
     */
    public int peek() {
        return ((Integer) runStack.lastElement());
    }

    /**
     * Pops (removes) the top item from the runtime stack and returns it
     *
     * @return the top item on the stack
     */
    public int pop() {
        int index = runStack.size() - 1;
        if( index == framePointers.peek() )
            framePointers.pop();
        return (Integer) runStack.remove(index);
    }

    /**
     * Places an item on the stack
     *
     * @param i the item to be pushed onto the stack
     * @return the item just pushed
     */
    public int push( int i ) {
        runStack.add(new Integer(i));
        return i;
    }

    /**
     * Gets the size of the runtime stack
     *
     * @return Size of runtime stack
     */
    public int size() {
        return runStack.size();
    }

    /**
     * Start new frame / activation record
     *
     * @param offset the number of slots down from the top of the RunTimeStack for
     *        starting the new frame - typically the number of arguments
     */
    public void newFrameAt( int offset ) {
        // ERROR HANDLING
        // Stack underflow or underflow
        if( runStack.size() - offset < 0 || offset < 0 )
            return;

        framePointers.add( new Integer( runStack.size() - offset) );
    }

    /**
     * We pop the top frame when we return from a function; before popping, the
     * function's return value is at the top of the stack so we'll save the value,
     * pop the top frame and then push the return value
     */
    public void popFrame() {
        int returnVal = pop();

        // ERROR HANDLING:
        // If there are more elements left, pop (remove) them
        //  ((Integer) framePointers.peek()).intValue() > runStack.size()
        while( runStack.size() - 1 >= (Integer) framePointers.peek() )
            pop();

        framePointers.pop(); // pops framepointer stack

        push( returnVal );
    }

    /**
     * Returns the index of the latest frame pointer - DOES NOT REMOVE IT
     * @return The index of the latest frame
     */
    public int getTopFrame() {
        if( framePointers.isEmpty() ) framePointers.add( new Integer(0) );
        return (Integer) framePointers.peek();
    }

    /**
     * Returns the element at index <code>index</code> on the runtime stack
     * @param index the index to look at
     * @return the element at position index on the runtime stack
     */
    public int getStackElementAt( int index ) {
        return (Integer) runStack.get(index);
    }

    /**
     * Stores the top of the runtime stack onto the given offset from the active
     * frame.
     *
     * @param offset which spot in the stack to save in
     * @return the new value of the given offset
     */
    public int store( int offset ) {
        // ERROR HANDLING
        // Checks for illegal offsets
        if( offset > runStack.size() || offset < 0 )
            return Integer.MIN_VALUE; // Easy to spot error

        Integer poppedElem = pop();

        runStack.setElementAt( poppedElem, offset + (Integer) framePointers.peek() );

        return poppedElem;
    }

    /**
     * Used to load variables onto the stack
     *
     * @param offset which spot in the stack to load from
     * @return the variable loaded
     */
    public int load( int offset ) {
        if( framePointers.isEmpty() ) framePointers.add( new Integer(0) );
        runStack.add(runStack.get(offset + (Integer) framePointers.peek() ) );
        
        return peek();
    }

    /**
     * Convenience method
     *
     * Used to load literals onto the stack - i.e. for lit 5 we'll call push with 5
     * 
     * @param i the literal to be pushed
     * @return the literal pushed
     */
    public Integer push( Integer i ) {

        return (Integer) push(i.intValue());
    }

    public int argCount() {
        return size() - framePointers.size();
    }

    /**
     * Prints out a representation of the runtime stack, divided up into activation records (frames)
     *
     * @return the string representation of the runtime stack
     */
    public String toString() {
        String out = "[";

        // Puts together a representation of the runtime stack
        Iterator it = runStack.iterator();
        int count = 0;
        while( it.hasNext()) {
            out += it.next();
            if( framePointers.contains( count+1 ))
                out += "] [";
            else if(it.hasNext()) out += ",";
            count++;
        }
        out += "]";

        return out;
    }
}
